#include "postgres.h"

#include <math.h>

#include "fmgr.h"
#include "plperl.h"
#include "plperl_helpers.h"
#include "utils/fmgrprotos.h"
#include "utils/jsonb.h"

PG_MODULE_MAGIC;

static SV  *fc_Jsonb_to_SV(JsonbContainer *fc_jsonb);
static JsonbValue *fc_SV_to_JsonbValue(SV *fc_obj, JsonbParseState **fc_ps, bool fc_is_elem);


static SV  * fc_JsonbValue_to_SV(JsonbValue *fc_jbv)
{
	dTHX;

	switch (fc_jbv->type)
	{
		case jbvBinary:
			return fc_Jsonb_to_SV(fc_jbv->val.binary.data);

		case jbvNumeric:
			{
				char	   *fc_str = DatumGetCString(DirectFunctionCall1(numeric_out,
																	  NumericGetDatum(fc_jbv->val.numeric)));
				SV		   *fc_result = newSVnv(SvNV(cstr2sv(fc_str)));

				pfree(fc_str);
				return fc_result;
			}

		case jbvString:
			{
				char	   *fc_str = pnstrdup(fc_jbv->val.string.val,
										   fc_jbv->val.string.len);
				SV		   *fc_result = cstr2sv(fc_str);

				pfree(fc_str);
				return fc_result;
			}

		case jbvBool:
			return newSVnv(SvNV(fc_jbv->val.boolean ? &PL_sv_yes : &PL_sv_no));

		case jbvNull:
			return newSV(0);

		default:
			elog(ERROR, "unexpected jsonb value type: %d", fc_jbv->type);
			return NULL;
	}
}

static SV  * fc_Jsonb_to_SV(JsonbContainer *fc_jsonb)
{
	dTHX;
	JsonbValue	fc_v;
	JsonbIterator *fc_it;
	JsonbIteratorToken fc_r;

	fc_it = JsonbIteratorInit(fc_jsonb);
	fc_r = JsonbIteratorNext(&fc_it, &fc_v, true);

	switch (fc_r)
	{
		case WJB_BEGIN_ARRAY:
			if (fc_v.val.array.rawScalar)
			{
				JsonbValue	fc_tmp;

				if ((fc_r = JsonbIteratorNext(&fc_it, &fc_v, true)) != WJB_ELEM ||
					(fc_r = JsonbIteratorNext(&fc_it, &fc_tmp, true)) != WJB_END_ARRAY ||
					(fc_r = JsonbIteratorNext(&fc_it, &fc_tmp, true)) != WJB_DONE)
					elog(ERROR, "unexpected jsonb token: %d", fc_r);

				return fc_JsonbValue_to_SV(&fc_v);
			}
			else
			{
				AV		   *fc_av = newAV();

				while ((fc_r = JsonbIteratorNext(&fc_it, &fc_v, true)) != WJB_DONE)
				{
					if (fc_r == WJB_ELEM)
						av_push(fc_av, fc_JsonbValue_to_SV(&fc_v));
				}

				return newRV((SV *) fc_av);
			}

		case WJB_BEGIN_OBJECT:
			{
				HV		   *fc_hv = newHV();

				while ((fc_r = JsonbIteratorNext(&fc_it, &fc_v, true)) != WJB_DONE)
				{
					if (fc_r == WJB_KEY)
					{
						/* json key in v, json value in val */
						JsonbValue	fc_val;

						if (JsonbIteratorNext(&fc_it, &fc_val, true) == WJB_VALUE)
						{
							SV		   *fc_value = fc_JsonbValue_to_SV(&fc_val);

							(void) hv_store(fc_hv,
											fc_v.val.string.val, fc_v.val.string.len,
											fc_value, 0);
						}
					}
				}

				return newRV((SV *) fc_hv);
			}

		default:
			elog(ERROR, "unexpected jsonb token: %d", fc_r);
			return NULL;
	}
}

static JsonbValue * fc_AV_to_JsonbValue(AV *fc_in, JsonbParseState **fc_jsonb_state)
{
	dTHX;
	SSize_t		fc_pcount = av_len(fc_in) + 1;
	SSize_t		fc_i;

	pushJsonbValue(fc_jsonb_state, WJB_BEGIN_ARRAY, NULL);

	for (fc_i = 0; fc_i < fc_pcount; fc_i++)
	{
		SV		  **fc_value = av_fetch(fc_in, fc_i, FALSE);

		if (fc_value)
			(void) fc_SV_to_JsonbValue(*fc_value, fc_jsonb_state, true);
	}

	return pushJsonbValue(fc_jsonb_state, WJB_END_ARRAY, NULL);
}

static JsonbValue * fc_HV_to_JsonbValue(HV *fc_obj, JsonbParseState **fc_jsonb_state)
{
	dTHX;
	JsonbValue	fc_key;
	SV		   *fc_val;
	char	   *fc_kstr;
	I32			fc_klen;

	fc_key.type = jbvString;

	pushJsonbValue(fc_jsonb_state, WJB_BEGIN_OBJECT, NULL);

	(void) hv_iterinit(fc_obj);

	while ((fc_val = hv_iternextsv(fc_obj, &fc_kstr, &fc_klen)))
	{
		fc_key.val.string.val = pnstrdup(fc_kstr, fc_klen);
		fc_key.val.string.len = fc_klen;
		pushJsonbValue(fc_jsonb_state, WJB_KEY, &fc_key);
		(void) fc_SV_to_JsonbValue(fc_val, fc_jsonb_state, false);
	}

	return pushJsonbValue(fc_jsonb_state, WJB_END_OBJECT, NULL);
}

static JsonbValue * fc_SV_to_JsonbValue(SV *fc_in, JsonbParseState **fc_jsonb_state, bool fc_is_elem)
{
	dTHX;
	JsonbValue	fc_out;			/* 结果 */

	/* 递归解引用引用。 */
	while (SvROK(fc_in))
		fc_in = SvRV(fc_in);

	switch (SvTYPE(fc_in))
	{
		case SVt_PVAV:
			return fc_AV_to_JsonbValue((AV *) fc_in, fc_jsonb_state);

		case SVt_PVHV:
			return fc_HV_to_JsonbValue((HV *) fc_in, fc_jsonb_state);

		default:
			if (!SvOK(fc_in))
			{
				fc_out.type = jbvNull;
			}
			else if (SvUOK(fc_in))
			{
				/*
				 * If UV is >=64 bits, we have no better way to make this
				 * happen than converting to text and back.  Given the low
				 * usage of UV in Perl code, it's not clear it's worth working
				 * hard to provide alternate code paths.
				 */
				const char *fc_strval = SvPV_nolen(fc_in);

				fc_out.type = jbvNumeric;
				fc_out.val.numeric =
					DatumGetNumeric(DirectFunctionCall3(numeric_in,
														CStringGetDatum(fc_strval),
														ObjectIdGetDatum(InvalidOid),
														Int32GetDatum(-1)));
			}
			else if (SvIOK(fc_in))
			{
				IV			fc_ival = SvIV(fc_in);

				fc_out.type = jbvNumeric;
				fc_out.val.numeric = int64_to_numeric(fc_ival);
			}
			else if (SvNOK(fc_in))
			{
				double		fc_nval = SvNV(fc_in);

				/*
				 * jsonb doesn't allow infinity or NaN (per JSON
				 * specification), but the numeric type that is used for the
				 * storage accepts those, so we have to reject them here
				 * explicitly.
				 */
				if (isinf(fc_nval))
					ereport(ERROR,
							(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
							 errmsg("cannot convert infinity to jsonb")));
				if (isnan(fc_nval))
					ereport(ERROR,
							(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
							 errmsg("cannot convert NaN to jsonb")));

				fc_out.type = jbvNumeric;
				fc_out.val.numeric =
					DatumGetNumeric(DirectFunctionCall1(float8_numeric,
														Float8GetDatum(fc_nval)));
			}
			else if (SvPOK(fc_in))
			{
				fc_out.type = jbvString;
				fc_out.val.string.val = sv2cstr(fc_in);
				fc_out.val.string.len = strlen(fc_out.val.string.val);
			}
			else
			{
				/*
				 * XXX It might be nice if we could include the Perl type in
				 * the error message.
				 */
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot transform this Perl type to jsonb")));
				return NULL;
			}
	}

	/* Push result into 'jsonb_state' unless it is a raw scalar. */
	return *fc_jsonb_state
		? pushJsonbValue(fc_jsonb_state, fc_is_elem ? WJB_ELEM : WJB_VALUE, &fc_out)
		: memcpy(palloc(sizeof(JsonbValue)), &fc_out, sizeof(JsonbValue));
}


PG_FUNCTION_INFO_V1(jsonb_to_plperl);

Datum jsonb_to_plperl(PG_FUNCTION_ARGS)
{
	dTHX;
	Jsonb	   *fc_in = PG_GETARG_JSONB_P(0);
	SV		   *fc_sv = fc_Jsonb_to_SV(&fc_in->root);

	return PointerGetDatum(fc_sv);
}


PG_FUNCTION_INFO_V1(plperl_to_jsonb);

Datum plperl_to_jsonb(PG_FUNCTION_ARGS)
{
	dTHX;
	JsonbParseState *fc_jsonb_state = NULL;
	SV		   *fc_in = (SV *) PG_GETARG_POINTER(0);
	JsonbValue *fc_out = fc_SV_to_JsonbValue(fc_in, &fc_jsonb_state, true);
	Jsonb	   *fc_result = JsonbValueToJsonb(fc_out);

	PG_RETURN_JSONB_P(fc_result);
}
